
package org.japril.jutil;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.japril.jutil.CommandLineParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * <p>
 *   Utility methods of use when working with the DOM XML parsing API.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC)
 */
public final class DomUtil 
{
  private DomUtil(){} // Utility pattern dictates private constructor.
  
  /**
   * <p>
   *   Parses XML using the DOM API.
   * </p>
   * @param is Stream to parse XML from.
   * @return {@link Document} that is parse result.
   * @throws IOException If fails to read or parse.
   */
  public static Document parse(File f) throws IOException
  {
    if (f==null)
    {
      throw new NullPointerException("f");
    }
    
    InputStream is=null;
    try
    {
      is=new FileInputStream(f);
      return parse(is);
    }
    finally
    {
      if (is!=null) try { is.close(); } catch (IOException _){}
    }
  }
  
  /**
   * <p>
   *   Parses XML using the DOM API.
   * </p>
   * @param is Stream to parse XML from.
   * @return {@link Document} that is parse result.
   * @throws IOException If fails to read or parse.
   */
  public static Document parse(InputStream is) throws IOException
  {
    if (is==null)
    {
      throw new NullPointerException("is");  
    }
    
    DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    try
    {
      DocumentBuilder db=dbf.newDocumentBuilder();
      Document retVal=db.parse(is);
      retVal.normalize();
      return retVal;
    }
    catch (ParserConfigurationException pcx)
    {
      IOException iox=new IOException();
      iox.initCause(pcx);
      throw iox;
    }
    catch (SAXException saxx)
    {
      IOException iox=new IOException();
      iox.initCause(saxx);
      throw iox;
    }
  }
  
  public static String toString(Node root)
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);

    compileToString(root, "", pw);
    
    pw.flush();
    return caw.toString();
  }
  
  private static void compileToString(Node root, String indent, PrintWriter pw)
  {  
    StringBuilder sb=new StringBuilder();
    sb.append(indent).append("type=")
      .append(nodeTypeToString(root.getNodeType()));
    
    String x=getNonEmpty(root.getNodeName());
    if (x!=null) sb.append(", node-name=").append(x);
    
    x=getNonEmpty(root.getNodeValue());
    if (x!=null) sb.append(", node-value=").append(x);
    
    sb.append(", has-attributes=").append(root.hasAttributes());
    
    pw.println(sb);
    
    Element element=isElement(root);
    if (element!=null)
    {
      NamedNodeMap nnm=element.getAttributes();
      int n=nnm.getLength();
      for (int i=0; i<n; i++)
      {
        Node an=nnm.item(i);
        compileToString(an, indent+"  ", pw);
      }
    }
    
    NodeList nl=root.getChildNodes();
    int n=nl.getLength();
    for (int i=0; i<n; i++)
    {
      Node node=nl.item(i);
      compileToString(node, indent+"  ", pw);
    }
  }
  
  private static Element isElement(Node node)
  {
    if (node.getNodeType()==Node.ELEMENT_NODE) return (Element)node;
    return null;
  }
  
  private static final String getNonEmpty(String s)
  {
    if (s==null) return null;
    return s.trim().length()==0 ? null : s;
  }
  
  public static Node[] xPathQuery(Node node, String q)
  {
    try
    {
      XPathFactory xf=XPathFactory.newInstance();
      XPath path=xf.newXPath();
      XPathExpression exp=path.compile(q);
    
      NodeList r=(NodeList)exp.evaluate(node, XPathConstants.NODESET);
      
      int n=r.getLength();
      Node[] retVal=new Node[n];
      for (int i=0; i<n; i++) retVal[i]=r.item(i);
      
      return retVal;
    }
    catch (XPathExpressionException x)
    {
      IllegalArgumentException iax=new IllegalArgumentException();
      iax.initCause(x);
      throw iax;
    }
  }
  
  public static String nodeTypeToString(short et)
  {
         if (et==Node.ATTRIBUTE_NODE)         return "ATTRIBUTE_NODE";
    else if (et==Node.CDATA_SECTION_NODE)     return "CDATA_SECTION_NODE";
    else if (et==Node.COMMENT_NODE)           return "COMMENT_NODE";
    else if (et==Node.DOCUMENT_FRAGMENT_NODE) return "DOCUMENT_FRAGMENT_NODE";
    else if (et==Node.DOCUMENT_TYPE_NODE)     return "DOCUMENT_TYPE_NODE";
    else if (et==Node.ELEMENT_NODE)           return "ELEMENT_NODE";
    else if (et==Node.ENTITY_NODE)            return "ENTITY_NODE";
    else if (et==Node.ENTITY_REFERENCE_NODE)  return "ENTITY_REFERENCE_NODE";
    else if (et==Node.NOTATION_NODE)          return "NOTATION_NODE";
    else if (et==Node.PROCESSING_INSTRUCTION_NODE) return "PROCESSING_INSTRUCTION_NODE";
    else if (et==Node.TEXT_NODE)                   return "TEXT_NODE";
    else
    {
      return "-Uknown-";
    }
  }
  
  public static void main(String[] args)
  {
    try
    {
      CommandLineParser clp=new CommandLineParser(args);
      File f=clp.fileValue("-in", new File("./test-data/unified.xml"));
      Document d=parse(f);
      
      String q="//account/orb/semantic-field/group";
      Node[] nodes=xPathQuery(d, q);
      for (Node node : nodes)
      {
        System.out.println("---");
        System.out.println(toString(node));
      }
      
      //System.out.println(toString(d));
    }
    catch (IOException iox)
    {
      iox.printStackTrace();
      System.exit(-1);
    }
  }
  
}
